<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>冷链运输开关门分析与能耗优化（热力学模型）</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
      /* 基于 MPU6050 风格的深色玻璃质感主题 */
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      }
      html,
      body {
        height: 100%;
      }
      body {
        /* background: linear-gradient(135deg, #0f3460 0%, #16213e 50%, #0b1020 100%); */
        color: #e6eef8;
        -webkit-font-smoothing: antialiased;
        padding: 20px;
      }
      .container {
        max-width: 1250px;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 1fr;
        gap: 20px;
      }
      header {
        background: rgba(255, 255, 255, 0.03);
        border: 1px solid rgba(255, 255, 255, 0.06);
        padding: 18px;
        border-radius: 14px;
        text-align: center;
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(6px);
      }
      h1 {
        font-size: 2rem;
        background: linear-gradient(90deg, #00c9ff, #92fe9d);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
      }
      .subtitle {
        color: rgba(230, 238, 248, 0.8);
        margin-top: 6px;
        font-size: 0.95rem;
      }

      .dashboard {
        display: grid;
        grid-template-columns: 420px 1fr;
        gap: 20px;
        align-items: start;
      }
      @media (max-width: 1000px) {
        .dashboard {
          grid-template-columns: 1fr;
        }
      }

      .panel {
        background: rgba(255, 255, 255, 0.03);
        border-radius: 12px;
        padding: 16px;
        border: 1px solid rgba(255, 255, 255, 0.04);
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(8px);
      }

      .panel-title {
        font-size: 1.05rem;
        color: #00c9ff;
        margin-bottom: 12px;
        display: flex;
        align-items: center;
        padding-bottom: 8px;
        border-bottom: 1px solid rgba(255, 255, 255, 0.03);
      }

      .parameter {
        margin-bottom: 12px;
        color: rgba(230, 238, 248, 0.9);
      }
      .parameter label {
        display: block;
        margin-bottom: 6px;
        font-weight: 600;
        opacity: 0.95;
      }
      .slider-container {
        display: flex;
        align-items: center;
        gap: 8px;
      }
      input[type="range"] {
        width: 100%;
        appearance: none;
        height: 8px;
        border-radius: 6px;
        background: rgba(255, 255, 255, 0.06);
      }
      input[type="range"]::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 18px;
        height: 18px;
        border-radius: 50%;
        background: #00c9ff;
        box-shadow: 0 4px 14px rgba(0, 201, 255, 0.18);
      }
      input[type="number"] {
        background: rgba(0, 0, 0, 0.2);
        color: #e6eef8;
        border: 1px solid rgba(255, 255, 255, 0.04);
        padding: 6px 10px;
        border-radius: 6px;
      }

      .slider-value {
        width: 80px;
        text-align: right;
        color: rgba(230, 238, 248, 0.9);
      }

      .btn-simulate {
        display: block;
        width: 100%;
        padding: 12px;
        margin-top: 10px;
        background: linear-gradient(90deg, #00c9ff, #92fe9d);
        color: #0b1020;
        border: none;
        border-radius: 8px;
        font-weight: 700;
        cursor: pointer;
        box-shadow: 0 8px 30px rgba(0, 201, 255, 0.12);
      }
      .btn-simulate:disabled {
        opacity: 0.6;
        cursor: not-allowed;
      }

      .visualization {
        display: flex;
        flex-direction: column;
        gap: 14px;
      }
      .chart-container {
        position: relative;
        height: 360px;
        border-radius: 10px;
        overflow: hidden;
        border: 1px solid rgba(255, 255, 255, 0.03);
        background: linear-gradient(
          180deg,
          rgba(255, 255, 255, 0.01),
          rgba(255, 255, 255, 0)
        );
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
        padding: 8px;
      }

      .results {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
        gap: 16px;
        margin-top: 6px;
      }
      .result-card {
        text-align: center;
        padding: 18px;
        background: rgba(255, 255, 255, 0.02);
        border-radius: 10px;
        border: 1px solid rgba(255, 255, 255, 0.03);
      }
      .result-value {
        font-size: 1.6rem;
        font-weight: 700;
        color: #92fe9d;
        margin: 8px 0;
      }
      .result-unit {
        font-size: 0.9rem;
        color: rgba(230, 238, 248, 0.7);
      }

      .recommendations {
        margin-top: 10px;
        padding: 12px;
        border-radius: 10px;
        background: rgba(255, 255, 255, 0.02);
        border: 1px solid rgba(255, 255, 255, 0.03);
      }
      .recommendation-item {
        padding: 8px 0;
        border-bottom: 1px solid rgba(255, 255, 255, 0.02);
        color: rgba(230, 238, 248, 0.9);
      }
      .recommendation-item:before {
        content: "💡";
        margin-right: 10px;
      }

      .small {
        font-size: 0.9rem;
        color: rgba(230, 238, 248, 0.7);
        margin-top: 6px;
      }

      /* Chart legend and grid tweaks for better contrast */
      .chartjs-render-monitor {
        background: transparent !important;
      }

      /* 滚动条样式 */
      body::-webkit-scrollbar {
        width: 2px;
      }

      body::-webkit-scrollbar-track {
        background: rgba(255, 255, 255, 0.05);
        border-radius: 4px;
      }

      body::-webkit-scrollbar-thumb {
        background: #22282e;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <header>
        <h1>冷链运输开关门分析与能耗优化</h1>
        <p class="subtitle">
          基于传导与换气热平衡计算制冷负荷，支持蒙特卡洛重复运行与可配置货舱参数
        </p>
      </header>

      <div class="dashboard">
        <div class="panel">
          <div class="panel-title">模拟参数</div>

          <div class="parameter">
            <label for="doorCount">每日开门次数</label>
            <div class="slider-container">
              <input
                type="range"
                id="doorCount"
                min="0"
                max="200"
                value="20"
                class="slider"
              />
              <span class="slider-value" id="doorCountValue">20</span>
            </div>
          </div>

          <div class="parameter">
            <label for="doorDuration">平均每次开门时长（秒）</label>
            <div class="slider-container">
              <input
                type="range"
                id="doorDuration"
                min="10"
                max="600"
                value="60"
                class="slider"
              />
              <span class="slider-value" id="doorDurationValue">60</span>
            </div>
          </div>

          <div class="parameter">
            <label for="outsideTemp">外界温度 (°C)</label>
            <div class="slider-container">
              <input
                type="range"
                id="outsideTemp"
                min="-10"
                max="50"
                value="30"
                class="slider"
              />
              <span class="slider-value" id="outsideTempValue">30</span>
            </div>
          </div>

          <div class="parameter">
            <label for="outsideHumidity">外界湿度 (%)</label>
            <div class="slider-container">
              <input
                type="range"
                id="outsideHumidity"
                min="10"
                max="100"
                value="70"
                class="slider"
              />
              <span class="slider-value" id="outsideHumidityValue">70</span>
            </div>
          </div>

          <div class="parameter">
            <label for="targetTemp">车厢目标温度 (°C)</label>
            <div class="slider-container">
              <input
                type="range"
                id="targetTemp"
                min="-30"
                max="20"
                value="4"
                class="slider"
              />
              <span class="slider-value" id="targetTempValue">4</span>
            </div>
          </div>

          <div class="parameter">
            <label for="targetHumidity">车厢目标湿度 (%)</label>
            <div class="slider-container">
              <input
                type="range"
                id="targetHumidity"
                min="30"
                max="95"
                value="85"
                class="slider"
              />
              <span class="slider-value" id="targetHumidityValue">85</span>
            </div>
          </div>

          <div class="panel-title" style="margin-top: 8px">热力学参数</div>

          <div class="parameter">
            <label for="enclosureVolume">车厢体积 (m³)</label>
            <div class="slider-container">
              <input
                type="number"
                id="enclosureVolume"
                min="1"
                max="500"
                value="30"
                style="width: 120px"
              />
              <span class="slider-value" id="enclosureVolumeValue">30</span>
            </div>
          </div>

          <div class="parameter">
            <label for="enclosureArea">换热表面积 A (m²)</label>
            <div class="slider-container">
              <input
                type="number"
                id="enclosureArea"
                min="1"
                max="400"
                value="30"
                style="width: 120px"
              />
              <span class="slider-value" id="enclosureAreaValue">30</span>
            </div>
          </div>

          <div class="parameter">
            <label for="insulationU">隔热系数 U (W/m²·K)</label>
            <div class="slider-container">
              <input
                type="number"
                id="insulationU"
                min="0.01"
                step="0.01"
                value="0.5"
                style="width: 120px"
              />
              <span class="slider-value" id="insulationUValue">0.50</span>
            </div>
          </div>

          <div class="parameter">
            <label for="cargoMass">货物质量 (kg)</label>
            <div class="slider-container">
              <input
                type="number"
                id="cargoMass"
                min="0"
                step="1"
                value="500"
                style="width: 120px"
              />
              <span class="slider-value" id="cargoMassValue">500</span>
            </div>
          </div>

          <div class="parameter">
            <label for="cargoCp">货物比热 c (J/kg·K)</label>
            <div class="slider-container">
              <input
                type="number"
                id="cargoCp"
                min="100"
                step="100"
                value="3000"
                style="width: 120px"
              />
              <span class="slider-value" id="cargoCpValue">3000</span>
            </div>
          </div>

          <div class="parameter">
            <label for="doorFlow">开门时空气交换流量 (m³/s)</label>
            <div class="slider-container">
              <input
                type="number"
                id="doorFlow"
                min="0"
                step="0.1"
                value="0.5"
                style="width: 120px"
              />
              <span class="slider-value" id="doorFlowValue">0.5</span>
            </div>
          </div>

          <div class="parameter">
            <label for="cop">制冷机 COP</label>
            <div class="slider-container">
              <input
                type="number"
                id="cop"
                min="1"
                step="0.1"
                value="3.0"
                style="width: 120px"
              />
              <span class="slider-value" id="copValue">3.0</span>
            </div>
          </div>

          <div class="panel-title" style="margin-top: 8px">
            蒙特卡洛（可选）
          </div>

          <div class="parameter">
            <label><input type="checkbox" id="mcEnable" /> 启用蒙特卡洛</label>
            <div class="small">重复运行以评估不确定性</div>
          </div>

          <div class="parameter">
            <label for="mcRuns">运行次数</label>
            <div class="slider-container">
              <input
                type="range"
                id="mcRuns"
                min="1"
                max="200"
                step="1"
                value="10"
                class="slider"
              />
              <span class="slider-value" id="mcRunsValue">10</span>
            </div>
          </div>

          <div class="parameter">
            <label for="rngSeed">随机种子（空为随机）</label>
            <div class="slider-container">
              <input type="number" id="rngSeed" style="width: 120px" />
              <span class="slider-value" id="rngSeedValue">-</span>
            </div>
          </div>

          <button id="simulateBtn" class="btn-simulate">运行模拟</button>
          <div class="small">
            提示：参数为估算项，若需严谨设计请补充设备额定功率与实验数据。
          </div>
        </div>

        <div class="visualization">
          <div class="panel">
            <div class="panel-title">温度曲线</div>
            <div class="chart-container">
              <canvas id="tempChart"></canvas>
            </div>
          </div>

          <div class="panel">
            <div class="panel-title">湿度曲线</div>
            <div class="chart-container">
              <canvas id="humidityChart"></canvas>
            </div>
          </div>

          <div class="results">
            <div class="result-card">
              <div class="panel-title" style="border-bottom: none">
                预计每日能耗
              </div>
              <div class="result-value" id="energyCost">--</div>
              <div class="result-unit">kWh / 天（电能）</div>
            </div>

            <div class="result-card">
              <div class="panel-title" style="border-bottom: none">
                温度波动范围
              </div>
              <div class="result-value" id="tempRange">--</div>
              <div class="result-unit">°C（最大-最小）</div>
            </div>

            <div class="result-card">
              <div class="panel-title" style="border-bottom: none">
                湿度波动范围
              </div>
              <div class="result-value" id="humidityRange">--</div>
              <div class="result-unit">%（最大-最小）</div>
            </div>

            <div class="result-card">
              <div class="panel-title" style="border-bottom: none">
                峰值制冷功率需求
              </div>
              <div class="result-value" id="peakPower">--</div>
              <div class="result-unit">kW（估计）</div>
            </div>
          </div>

          <div class="recommendations panel" style="margin-top: 8px">
            <div class="panel-title">优化建议</div>
            <div class="recommendation-list" id="recommendationList"></div>
          </div>
        </div>
      </div>
    </div>

    <script>
      // 简洁工具
      const el = (id) => document.getElementById(id);
      const setText = (id, v) => {
        const e = el(id);
        if (e) e.textContent = v;
      };

      // 同步 UI 显示
      const syncMap = [
        ["doorCount"],
        ["doorDuration"],
        ["outsideTemp"],
        ["outsideHumidity"],
        ["targetTemp"],
        ["targetHumidity"],
        ["enclosureVolume"],
        ["enclosureArea"],
        ["insulationU"],
        ["cargoMass"],
        ["cargoCp"],
        ["doorFlow"],
        ["cop"],
        ["mcRuns"],
      ];
      syncMap.forEach(([id]) => {
        const inp = el(id);
        const disp = el(id + "Value");
        if (!inp || !disp) return;
        const update = () => (disp.textContent = inp.value);
        inp.addEventListener("input", update);
        update();
      });
      el("rngSeed")?.addEventListener("input", () =>
        setText("rngSeedValue", el("rngSeed").value || "-")
      );

      // Chart.js 初始化（配色调整为霓虹风格）
      const tempCtx = el("tempChart").getContext("2d");
      const humidityCtx = el("humidityChart").getContext("2d");

      const tempChart = new Chart(tempCtx, {
        type: "line",
        data: {
          labels: [],
          datasets: [
            {
              label: "舱内温度 (°C)",
              data: [],
              borderColor: "#ff6b6b",
              backgroundColor: "rgba(255,107,107,0.08)",
              tension: 0.25,
              pointRadius: 0,
            },
            {
              label: "目标温度 (°C)",
              data: [],
              borderColor: "#00c9ff",
              borderDash: [6, 4],
              backgroundColor: "transparent",
              tension: 0,
              pointRadius: 0,
            },
          ],
        },
        options: {
          animation: false,
          responsive: true,
          maintainAspectRatio: false,
          plugins: { legend: { labels: { color: "rgba(230,238,248,0.9)" } } },
          scales: {
            x: { ticks: { color: "rgba(230,238,248,0.6)" } },
            y: { ticks: { color: "rgba(230,238,248,0.6)" } },
          },
        },
      });

      const humidityChart = new Chart(humidityCtx, {
        type: "line",
        data: {
          labels: [],
          datasets: [
            {
              label: "舱内湿度 (%)",
              data: [],
              borderColor: "#92fe9d",
              backgroundColor: "rgba(146,254,157,0.06)",
              tension: 0.25,
              pointRadius: 0,
            },
            {
              label: "目标湿度 (%)",
              data: [],
              borderColor: "#9b59b6",
              borderDash: [6, 4],
              backgroundColor: "transparent",
              tension: 0,
              pointRadius: 0,
            },
          ],
        },
        options: {
          animation: false,
          responsive: true,
          maintainAspectRatio: false,
          plugins: { legend: { labels: { color: "rgba(230,238,248,0.9)" } } },
          scales: {
            x: { ticks: { color: "rgba(230,238,248,0.6)" } },
            y: { ticks: { color: "rgba(230,238,248,0.6)" } },
          },
        },
      });

      // 配置参数
      const CONFIG = {
        intervalSec: 300, // 步长 5 分钟
        defaultCOP: 3.0,
        rhoAir: 1.2, // kg/m3
        cpAir: 1005, // J/(kg·K)
        randomTempNoise: 0.2,
        randomHumNoise: 0.6,
      };

      // 可复现 RNG (mulberry32)
      function createRNG(seed) {
        if (seed === undefined || seed === null || Number.isNaN(Number(seed)))
          return Math.random;
        let t = Number(seed) >>> 0;
        return function () {
          t += 0x6d2b79f5;
          let r = Math.imul(t ^ (t >>> 15), 1 | t);
          r ^= r + Math.imul(r ^ (r >>> 7), r | 61);
          return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
        };
      }

      // 生成开门 mask
      function generateDoorMask(steps, doorCount, doorDurationSec, rng) {
        const mask = new Uint8Array(steps);
        if (doorCount <= 0) return mask;
        const intervalSec = CONFIG.intervalSec;
        const durSteps = Math.max(1, Math.round(doorDurationSec / intervalSec));
        for (let k = 0; k < doorCount; k++) {
          const maxStart = Math.max(0, steps - durSteps);
          const r = rng();
          const start = Math.floor(r * (maxStart + 1));
          for (let s = start; s < Math.min(steps, start + durSteps); s++)
            mask[s] = 1;
        }
        return mask;
      }

      // 单次完整模拟
      function runSingleSimulation(params, rng) {
        const steps = Math.round((24 * 3600) / CONFIG.intervalSec);
        const openMask = generateDoorMask(
          steps,
          params.doorCount,
          params.doorDuration,
          rng
        );

        const tempData = new Array(steps);
        const humidityData = new Array(steps);
        let currentTemp = params.targetTemp;
        let currentHumidity = params.targetHumidity;

        const airMass = (params.enclosureVolume || 30) * CONFIG.rhoAir; // kg
        const cargoMass = params.cargoMass || 0; // kg
        const cargoCp = params.cargoCp || 3000; // J/kgK
        const C = airMass * CONFIG.cpAir + cargoMass * cargoCp; // J/K

        const U = params.insulationU || 0.5; // W/m2K
        const A = params.enclosureArea || 30; // m2
        const doorFlow = params.doorFlow || 0.5; // m3/s
        const COP = params.cop || CONFIG.defaultCOP;

        const dt = CONFIG.intervalSec;
        let energyKwh = 0;
        let peakPowerW = 0;

        for (let i = 0; i < steps; i++) {
          const Q_cond = U * A * (params.outsideTemp - currentTemp);
          const Q_inf = openMask[i]
            ? CONFIG.rhoAir *
              CONFIG.cpAir *
              doorFlow *
              (params.outsideTemp - currentTemp)
            : 0;
          const Q_net = Q_cond + Q_inf;

          const dT = (Q_net * dt) / C;
          currentTemp += dT;

          currentTemp += (rng() - 0.5) * CONFIG.randomTempNoise;
          currentHumidity += (rng() - 0.5) * CONFIG.randomHumNoise;
          currentHumidity = Math.max(10, Math.min(99, currentHumidity));

          if (currentTemp > params.targetTemp) {
            const Q_remove_W = (C * (currentTemp - params.targetTemp)) / dt; // W
            const electricalPowerW = Q_remove_W / COP;
            energyKwh += (electricalPowerW / 1000) * (dt / 3600); // kWh
            peakPowerW = Math.max(peakPowerW, electricalPowerW);
            currentTemp = params.targetTemp;
          }

          tempData[i] = parseFloat(currentTemp.toFixed(2));
          humidityData[i] = parseFloat(currentHumidity.toFixed(1));
        }

        const tempMin = Math.min(...tempData),
          tempMax = Math.max(...tempData);
        const humMin = Math.min(...humidityData),
          humMax = Math.max(...humidityData);

        return {
          tempData,
          humidityData,
          energyKwh: +energyKwh.toFixed(3),
          tempRange: +(tempMax - tempMin).toFixed(2),
          humidityRange: +(humMax - humMin).toFixed(1),
          peakPowerKw: +(peakPowerW / 1000).toFixed(3),
        };
      }

      // 蒙特卡洛
      function runMonteCarlo(params, runs, seed) {
        const steps = Math.round((24 * 3600) / CONFIG.intervalSec);
        const sumTemp = new Array(steps).fill(0);
        const sumHum = new Array(steps).fill(0);
        let sumEnergy = 0,
          sumEnergy2 = 0;
        let maxPeakKw = 0;
        for (let i = 0; i < runs; i++) {
          const rng = createRNG(
            seed !== undefined && seed !== "" ? Number(seed) + i : undefined
          );
          const r = runSingleSimulation(params, rng);
          for (let s = 0; s < steps; s++) {
            sumTemp[s] += r.tempData[s];
            sumHum[s] += r.humidityData[s];
          }
          sumEnergy += r.energyKwh;
          sumEnergy2 += r.energyKwh * r.energyKwh;
          maxPeakKw = Math.max(maxPeakKw, r.peakPowerKw);
        }
        const avgTemp = sumTemp.map((v) => +(v / runs).toFixed(2));
        const avgHum = sumHum.map((v) => +(v / runs).toFixed(1));
        const meanEnergy = sumEnergy / runs;
        const variance = Math.max(
          0,
          sumEnergy2 / runs - meanEnergy * meanEnergy
        );
        const stdEnergy = Math.sqrt(variance);
        return {
          avgTemp,
          avgHum,
          meanEnergy: +meanEnergy.toFixed(3),
          stdEnergy: +stdEnergy.toFixed(3),
          maxPeakKw,
        };
      }

      // 更新图表
      function updateCharts(
        labels,
        tempSeries,
        targetTempSeries,
        humSeries,
        targetHumSeries
      ) {
        tempChart.data.labels = labels;
        tempChart.data.datasets[0].data = tempSeries;
        tempChart.data.datasets[1].data = targetTempSeries;
        tempChart.update();

        humidityChart.data.labels = labels;
        humidityChart.data.datasets[0].data = humSeries;
        humidityChart.data.datasets[1].data = targetHumSeries;
        humidityChart.update();
      }

      // 更新结果卡片与建议
      function updateResults(energyKwh, tempRange, humRange, peakKw, mcInfo) {
        setText("energyCost", energyKwh !== null ? energyKwh.toFixed(3) : "--");
        setText("tempRange", tempRange !== null ? tempRange : "--");
        setText("humidityRange", humRange !== null ? humRange : "--");
        setText("peakPower", peakKw !== null ? peakKw.toFixed(3) : "--");

        const rec = [];
        if (mcInfo && mcInfo.runs > 1) {
          rec.push(
            `蒙特卡洛 ${mcInfo.runs} 次：平均能耗 ${mcInfo.meanEnergy} kWh，标准差 ${mcInfo.stdEnergy} kWh。`
          );
        }
        if (energyKwh > 20)
          rec.push(
            `预计每日能耗偏高（${energyKwh.toFixed(
              1
            )} kWh），建议检查装卸流程与隔热性能。`
          );
        if (tempRange > 4)
          rec.push(
            `温度波动较大 (${tempRange}°C)，可能影响货品质量，建议减少开门次数或缩短时长。`
          );
        if (humRange > 20)
          rec.push(`湿度波动较大 (${humRange}%)，建议检查密封及车内除湿策略。`);
        if (rec.length === 0)
          rec.push(
            "当前参数下无明显异常，仍建议定期验证设备额定性能与实际数据。"
          );

        const list = el("recommendationList");
        list.innerHTML = "";
        rec.forEach((r) => {
          const d = document.createElement("div");
          d.className = "recommendation-item";
          d.innerHTML = r;
          list.appendChild(d);
        });
      }

      function setRunning(flag) {
        el("simulateBtn").disabled = flag;
      }

      function makeTimeLabels() {
        const stepSec = CONFIG.intervalSec;
        const labels = [];
        const steps = Math.round((24 * 3600) / stepSec);
        for (let i = 0; i < steps; i++) {
          const t = i * stepSec;
          const h = Math.floor(t / 3600) % 24;
          const m = Math.floor((t % 3600) / 60);
          labels.push(
            `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`
          );
        }
        return labels;
      }

      async function simulateHandler() {
        setRunning(true);
        try {
          const params = {
            doorCount: Number(el("doorCount").value),
            doorDuration: Number(el("doorDuration").value),
            outsideTemp: Number(el("outsideTemp").value),
            outsideHumidity: Number(el("outsideHumidity").value),
            targetTemp: Number(el("targetTemp").value),
            targetHumidity: Number(el("targetHumidity").value),
            enclosureVolume: Number(el("enclosureVolume").value),
            enclosureArea: Number(el("enclosureArea").value),
            insulationU: Number(el("insulationU").value),
            cargoMass: Number(el("cargoMass").value),
            cargoCp: Number(el("cargoCp").value),
            doorFlow: Number(el("doorFlow").value),
            cop: Number(el("cop").value),
          };

          const labels = makeTimeLabels();

          const mcEnable = el("mcEnable").checked;
          const runs = Number(el("mcRuns").value) || 1;
          const seedRaw = el("rngSeed").value;
          const seed = seedRaw === "" ? undefined : Number(seedRaw);

          if (mcEnable && runs > 1) {
            const result = runMonteCarlo(params, runs, seed);
            updateCharts(
              labels,
              result.avgTemp,
              Array(labels.length).fill(params.targetTemp),
              result.avgHum,
              Array(labels.length).fill(params.targetHumidity)
            );
            updateResults(result.meanEnergy, null, null, result.maxPeakKw, {
              runs,
              meanEnergy: result.meanEnergy,
              stdEnergy: result.stdEnergy,
            });
          } else {
            const rng = createRNG(seed);
            const r = runSingleSimulation(params, rng);
            updateCharts(
              labels,
              r.tempData,
              Array(labels.length).fill(params.targetTemp),
              r.humidityData,
              Array(labels.length).fill(params.targetHumidity)
            );
            updateResults(
              r.energyKwh,
              r.tempRange,
              r.humidityRange,
              r.peakPowerKw,
              { runs: 1 }
            );
          }
        } catch (e) {
          console.error(e);
        } finally {
          setRunning(false);
        }
      }

      el("simulateBtn").addEventListener("click", simulateHandler);
      window.addEventListener("load", simulateHandler);
    </script>
  </body>
</html>
